package com.huangyi.chatservice.service.websocket;

import com.huangyi.chatservice.config.ServerEncoder;
import com.huangyi.chatservice.config.WebSocketConfig;
import com.huangyi.chatservice.domain.dto.MessageSendReqDTO;
import com.huangyi.chatservice.domain.dto.WebSocketDTO;
import com.huangyi.chatservice.domain.enums.MessageStatusEnum;
import com.huangyi.chatservice.domain.enums.WebSocketDataTypeEnum;
import com.huangyi.chatservice.service.SnowflakeIdService;
import com.huangyi.commoncore.utils.JsonUtil;
import com.huangyi.commondomain.domain.ResultCode;
import com.huangyi.commondomain.exception.ServiceException;
import com.huangyi.commonsecurity.dto.LoginUserDTO;
import com.huangyi.commonsecurity.service.TokenService;
import com.huangyi.commonsecurity.utils.SecurityUtil;
import com.huangyi.chatservice.service.mq.MessageProduce;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 服务端的 endpoint
 */
@Component
@Slf4j
@ServerEndpoint(value = "/websocket",
        configurator = WebSocketConfig.class,
        encoders = ServerEncoder.class)
public class WebSocketServer {

    /**
     * 建立连接后的会话对象
     */
    private Session session;

    private Long userId;

    private static MessageProduce messageProduce;

    //@Autowired 和 @Resource 是通过反射来创建的会报错
    private static TokenService tokenService;

    private static SnowflakeIdService snowflakeIdService;

    /**
     * Autowired 注解作用与方法
     * 当类实例化时，Spring 容器会自动解析方法的参数。并为参数找到与其匹配的 Bean 实例，然后调用这些方法并注入
     *
     * @param tokenService
     */
    @Autowired
    public void setTokenService(TokenService tokenService) {
        WebSocketServer.tokenService = tokenService;
    }

    @Autowired
    public void setMessageProduce(MessageProduce messageProduce) {
        WebSocketServer.messageProduce = messageProduce;
    }

    @Autowired
    public void setSnowflakeIdService(SnowflakeIdService snowflakeIdService) {
        WebSocketServer.snowflakeIdService = snowflakeIdService;
    }


    /**
     * 存放服务区和每个客户端对应的WebSocket对象。
     * 建立连接之后去设值，断开连接之后需要删除
     */
    private static ConcurrentHashMap<Long, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();


    /**
     * 成功建立连接后调用
     * @param session 会话信息
     * @throws IOException
     */
    @OnOpen
    public void onOpen(Session session) throws IOException {
        try {
            log.info("建立链接成功！");
            // 设置webSocketMap，最重要是如何获取到 userId。
            // 0. 目前已经通过 WebSocketConfig.modifyHandshake 将token设置进了session的UserProperties属性中。
            String token = (String)session.getUserProperties().get("Authorization");
            // Authorization: eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxODM0MTcwMzY0NjI5NTkwMDE3LCJ1c2VyX2tleSI6IjY0YTQ3NjUxLWI5OTAtNGI2NC05YWQ1LTM2MTE0ZDUyMWRiNiIsInVzZXJuYW1lIjpudWxsfQ.PEoqjvN4LzRsuZS8P4MTUxlbbt4bDhrjZ4fOd7b9tW_Lvit7jR8_Ynic-eqZoTg8Loc9Sq3QLQaegtWIHrDFzg
            // Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxODM0MTcwMzY0NjI5NTkwMDE3LCJ1c2VyX2tleSI6IjY0YTQ3NjUxLWI5OTAtNGI2NC05YWQ1LTM2MTE0ZDUyMWRiNiIsInVzZXJuYW1lIjpudWxsfQ.PEoqjvN4LzRsuZS8P4MTUxlbbt4bDhrjZ4fOd7b9tW_Lvit7jR8_Ynic-eqZoTg8Loc9Sq3QLQaegtWIHrDFzg
            // 1. 去除前缀
            token = SecurityUtil.replaceTokenPrefix(token);
            if (StringUtils.isBlank(token)) {
                throw new ServiceException("没有传递用户token！", ResultCode.INVALID_PARA.getCode());
            }

            // 2. 根据token解析出用户信息下的用户id
            LoginUserDTO loginUserDTO = tokenService.getLoginUser(token);
            if(loginUserDTO == null) {
                throw new ServiceException("用户token有误", ResultCode.INVALID_PARA.getCode());
            }

            // 设置ws属性
            this.userId = loginUserDTO.getUserId();
            this.session = session;

            // 管理连接
            webSocketMap.put(userId, this);
            //  log.info("已管理用户连接:{}", JsonUtil.obj2String(webSocketMap));
            log.info("用户{}已经连接", userId);
        }catch (Exception e) {
            log.error("连接出现异常, 关闭连接！", e);
            session.close();
        }
    }

    @OnClose
    public void onClose() throws IOException {
        // log.info("断开连接成功！");
        if (userId != null && webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
        }
        log.info("用户{}已经关闭连接", userId);
        this.session = null;
        this.userId = null;
    }

    @OnError
    public void onError(Session session, Throwable error) {

    }

    @OnMessage
    public void onMessage(String message) {
        // 接收到客户端消息
        log.info("成功收到客户端消息！:{}", message);

        try {
            // 处理消息
            WebSocketDTO<?> webSocketDTO = JsonUtil.string2Obj(message, WebSocketDTO.class);
            if (null == webSocketDTO) {
                log.error("webSocket 不支持的协议！message:{}", message);
                return;
            }
            handleMessage(webSocketDTO.getType(), webSocketDTO.getData());
        } catch (Exception e) {
            log.error("消息推送失败！", e);
        }
    }


    private <T> void handleMessage(String type, T data) {
        WebSocketDataTypeEnum webSocketDataTypeEnum = WebSocketDataTypeEnum.getByType(type);
        if (null == webSocketDataTypeEnum) {
            handleUnknownMessage(type);
            return;
        }
        switch (webSocketDataTypeEnum) {
            case TEXT:
                handleTextMessage((String) data);
                break;
            case CHAT:
                handleChatMessage((String) data);
                break;
            case HEART_BEAT:
                handleHeartBeatMessage();
                break;
            default:
                handleUnknownMessage(type);
                break;
        }
    }

    private void handleHeartBeatMessage() {
        // 对应心跳消息来说，接收到谁的Ping 就返回给谁Pong
        WebSocketDTO<String> webSocketDTO = new WebSocketDTO<>(
                WebSocketDataTypeEnum.HEART_BEAT.getType(), "pong");
        sendMessage(webSocketDTO);
    }

    private void handleChatMessage(String data) {
        try {
            // 反序列化 聊天消息
            MessageSendReqDTO messageSendReqDTO = JsonUtil.string2Obj(data, MessageSendReqDTO.class);
            if(messageSendReqDTO == null) {
                throw new ServiceException("聊天消息为空！");
            }

            // 广播资讯聊天消息
            messageSendReqDTO.setMessageId(snowflakeIdService.nextId());
            messageSendReqDTO.setStatus(MessageStatusEnum.MESSAGE_UNREAD.getCode());
            messageSendReqDTO.setVisited(MessageStatusEnum.MESSAGE_NOT_VISITED.getCode());
            messageProduce.sendMessage(messageSendReqDTO);
        }catch (Exception e) {
            log.error("生产者发送消息异常，data:{}", data, e);
        }

    }

    private void handleUnknownMessage(String type) {
        log.error("无效的消息类型，无法处理！type:{}", type);
    }

    // TODO 文本消息的作用? 为什么不用 rabbitmq?
    // TODO 为什么只给当前用户发送? 为什么不想聊天类型的消息一样, 由 produce 生产, 由 consumer 消费？
    private void handleTextMessage(String data) {
        try {
            Thread.sleep(3000);
            String message = "服务端：" + data;
            sendMessage(new WebSocketDTO<>(WebSocketDataTypeEnum.TEXT.getType(), message));
        } catch (Exception e) {
            log.error("处理文本消息异常！", e);
        }

    }

    /**
     * 给当前连接会话推送消息, 给当前 WebSocketServer 发消息
     * 用于给当前用户发送 ping pong 消息, 不涉及 rabbitmq
     * 用于给当前用户发送 符合条件后的消费者消息
     *
     * @param webSocketDTO
     */
    private void sendMessage(WebSocketDTO<?> webSocketDTO) {
        try {
            this.session.getBasicRemote().sendObject(webSocketDTO);
        } catch (Exception e) {
            log.error("ws 消息推送失败，webSocketDTO:{}",
                    JsonUtil.obj2String(webSocketDTO), e);
        }

    }

    /**
     * 给指定用户推送消息（这里的用户是当前服务器自己管理的session）
     * 消息队列消费者获取 Produce 生产的消息后推送给目标用户
     * 判断传入的 toUserId 是否与当前用户 id 一致, 如果一致才发送
     * @param userId
     * @param webSocketDTO
     */
    public static void sendMessage(Long userId, WebSocketDTO<?> webSocketDTO) {
        if (!webSocketMap.containsKey(userId)) {
            // 无法推送，丢弃
            return;
        }

        webSocketMap.get(userId).sendMessage(webSocketDTO);
        log.info("消息转发成功:{}", JsonUtil.obj2String(webSocketDTO));
    }


}
